Skip to content

Conversation

@ArnabChatterjee20k
Copy link
Contributor

@ArnabChatterjee20k ArnabChatterjee20k commented Jan 9, 2026

Summary by CodeRabbit

  • New Features

    • TTL (Time-To-Live) indexes: per-index TTL values enable automatic expiration and removal of documents; runtime checks skip expired documents.
    • Adapter capability query to check TTL index support.
  • Validation

    • Enforces a single TTL index per collection and validates TTL index rules (attribute type, positive TTL, etc.).
  • Tests

    • Extensive unit and end-to-end tests covering TTL creation, validation, expiry, and duplicate prevention.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 9, 2026

📝 Walkthrough

Walkthrough

Adds TTL (time-to-live) index support across the DB layer: expands adapter createIndex signatures to accept collation and ttl, introduces adapter capability method getSupportForTTLIndexes(), integrates TTL validation, implements Mongo TTL behavior, propagates TTL through Mirror/Pool, and adds extensive tests and runtime expiry checks.

Changes

Cohort / File(s) Summary
Adapter Base & Interface
src/Database/Adapter.php
createIndex signature expanded to add array $collation = [] and int $ttl = 1; new getSupportForTTLIndexes(): bool (default false)
Adapter Implementations (stubs / no TTL)
src/Database/Adapter/MariaDB.php, src/Database/Adapter/MySQL.php, src/Database/Adapter/Postgres.php, src/Database/Adapter/Pool.php, src/Database/Adapter/SQLite.php
Aligned createIndex signatures to include $collation and $ttl; added getSupportForTTLIndexes() returning false
MongoDB Adapter (full TTL)
src/Database/Adapter/Mongo.php
createIndex signature extended with $ttl; implements getSupportForTTLIndexes() returning true; added helpers (shouldAddTenantToIndex, convertUTCDateToString, isExtendedISODatetime) and integrated TTL handling into collection/index creation, rename, and casting
Core DB & Mirror
src/Database/Database.php, src/Database/Mirror.php
Added INDEX_TTL constant; createIndex accepts ttl and stores it on index documents; enforces single TTL index per collection; passes TTL to adapters; Mirror propagates TTL to source/destination
Index Validator
src/Database/Validator/Index.php
Constructor gained supportForTTLIndexes flag; added checkTTLIndexes() and integrated TTL checks into validation (single datetime attribute, ttl > 0, no duplicates)
Tests — E2E & Unit
tests/e2e/Adapter/Scopes/SchemalessTests.php, tests/e2e/Adapter/Scopes/IndexTests.php, tests/unit/Validator/IndexTest.php
Large suite of TTL-focused tests added (creation, validation, duplicate prevention, expiry lifecycle, datetime/string handling, cache-aware expiry); unit test for TTL validation added
Composer
composer.json
Updated utopia-php/pools constraint from 0.8.* to 1.*

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant DB as Database
    participant Validator as IndexValidator
    participant Adapter as Adapter
    participant Store as DataStore

    Client->>DB: createIndex(collection, id, 'ttl', attrs..., ttl=3600)
    DB->>Validator: new(..., supportForTTLIndexes=Adapter.getSupportForTTLIndexes())
    DB->>Validator: isValid(index)
    Validator->>Validator: checkTTLIndexes(index)
    Validator-->>DB: valid/invalid
    alt valid
        DB->>DB: persist index document (includes ttl)
        DB->>Adapter: createIndex(..., collation, ttl=3600)
        Adapter->>Store: create index (expireAfterSeconds=3600) / provider-specific call
        Store-->>Adapter: success
        Adapter-->>DB: true
        DB-->>Client: index created
    else invalid
        DB-->>Client: throw IndexException
    end
Loading
sequenceDiagram
    participant Client as Client
    participant DB as Database
    participant Adapter as Adapter
    participant Store as DataStore

    Client->>DB: getDocument(collection, docId)
    DB->>DB: check Adapter.getSupportForTTLIndexes()
    alt supports TTL
        DB->>DB: load TTL index/meta (attribute, ttlSeconds)
        DB->>Adapter: fetch raw document
        Adapter->>Store: get document
        Store-->>Adapter: document
        Adapter-->>DB: document
        DB->>DB: parse document[ttlAttribute] as datetime
        DB->>DB: if now > parsed + ttlSeconds
        alt expired
            DB-->>Client: return empty/not-found
        else not expired
            DB-->>Client: return document
        end
    else not supported
        DB->>Adapter: fetch document
        Adapter-->>DB: document
        DB-->>Client: document
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • abnegate
  • fogelito

Poem

🐰 A Rabbit on TTL

I hopped through indexes, soft and spry,
Marked timestamps where old documents lie,
When seconds pass, I nudge—a gentle peal,
“Expire away!”—such is my zeal,
Hop, skip, the database sighs goodbye.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Mongo ttl index support' clearly and specifically describes the primary change: adding TTL (time-to-live) index support to the MongoDB adapter, which is the main objective.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ArnabChatterjee20k ArnabChatterjee20k marked this pull request as draft January 9, 2026 12:31
@ArnabChatterjee20k ArnabChatterjee20k marked this pull request as ready for review January 12, 2026 08:11
@ArnabChatterjee20k
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 12, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/Database/Adapter/MariaDB.php (1)

718-781: Fix PHPMD “unused parameter” warnings for createIndex().
Right now $indexAttributeTypes, $collation, $ttl are unused (and static analysis already flags this), which may fail CI.

Proposed minimal fix (no behavior change)
 public function createIndex(string $collection, string $id, string $type, array $attributes, array $lengths, array $orders, array $indexAttributeTypes = [], array $collation = [], int $ttl = 0): bool
 {
+    // Adapter doesn't use these yet; keep signature aligned with Adapter API.
+    unset($indexAttributeTypes, $collation, $ttl);
+
     $metadataCollection = new Document(['$id' => Database::METADATA]);
     $collection = $this->getDocument($metadataCollection, $collection);
src/Database/Mirror.php (1)

481-506: Bug: TTL value not included in document, causing data loss during mirroring.

The ttl attribute is not set when creating the document (lines 481-487), but line 506 attempts to read it. This will always return the default value 0, effectively losing the TTL configuration when mirroring indexes to the destination database.

🐛 Proposed fix
             $document = new Document([
                 '$id' => $id,
                 'type' => $type,
                 'attributes' => $attributes,
                 'lengths' => $lengths,
                 'orders' => $orders,
+                'ttl' => $ttl,
             ]);
src/Database/Database.php (1)

3600-3730: Add defensive TTL validation in createIndex() even when validation is disabled.

The checkTTLIndexes() validator is only applied when $this->validate is true (line 3682), allowing invalid TTL values to bypass validation and reach the adapter. When validation is disabled, negative TTL values, TTL on non-TTL indexes, or TTL on adapters without support will only fail at the adapter level instead of being caught early.

Add these checks before creating the index:

Proposed fix
 public function createIndex(string $collection, string $id, string $type, array $attributes, array $lengths = [], array $orders = [], int $ttl = 0): bool
 {
+        if ($ttl < 0) {
+            throw new DatabaseException('TTL must be greater than or equal to 0');
+        }
+
+        if ($type === self::INDEX_TTL && !$this->adapter->getSupportTTLIndexes()) {
+            throw new DatabaseException('TTL indexes are not supported by the current adapter');
+        }
+
+        if ($type !== self::INDEX_TTL && $ttl !== 0) {
+            throw new DatabaseException('TTL can only be set for ttl indexes');
+        }
+
         if (empty($attributes)) {
             throw new DatabaseException('Missing attributes');
         }

The adapter method signature matches the call: createIndex(..., $indexAttributesWithTypes, [], $ttl) correctly passes all 9 arguments in the expected order.

🤖 Fix all issues with AI agents
In @src/Database/Validator/Index.php:
- Around line 774-777: Fix the typo in the validation message: change the text
assigned to $this->message from 'TTL must be atleast 1 second' to 'TTL must be
at least 1 second' in the validation logic (the block that checks if ($ttl <=
0)) so the error message is grammatically correct; update the string where it's
set (reference: $this->message in Index.php's TTL validation).
- Around line 755-758: The error message set in the Index validator is
misleading: replace the current message "TTL index can be created on a single
object attribute" with one that correctly references datetime attributes (e.g.,
"TTL index can be created on a single datetime attribute") so that when the
validation in the Index validator (the block setting $this->message and
returning false) triggers, it accurately reflects that TTL indexes must target a
single datetime attribute (see the later datetime validation in this validator).

In @tests/unit/Validator/IndexTest.php:
- Around line 692-703: The assertion checks the wrong error text for the
TTL-on-non-datetime index case: update the expected substring in the test that
validates failure for the Document with 'type' => Database::INDEX_TTL (created
as $invalidIndexType) to match the TTL-specific message returned by the
validator (e.g., "TTL index can only be created on datetime attributes") by
replacing the current 'Object index can only be created on datetime attributes'
with the TTL wording when calling $this->assertStringContainsString(...,
$validator->getDescription()).
🧹 Nitpick comments (4)
tests/unit/Validator/IndexTest.php (1)

636-655: Prefer named args for Index validator construction (too many positional booleans).
This test is fragile to constructor re-ordering and hard to review.

Example refactor (adjust names to actual signature)
-        $validator = new Index(
-            $collection->getAttribute('attributes'),
-            $collection->getAttribute('indexes', []),
-            768,
-            [],
-            false, // supportForArrayIndexes
-            false, // supportForSpatialIndexNull
-            false, // supportForSpatialIndexOrder
-            false, // supportForVectorIndexes
-            true,  // supportForAttributes
-            true,  // supportForMultipleFulltextIndexes
-            true,  // supportForIdenticalIndexes
-            false, // supportForObjectIndexes
-            false, // supportForTrigramIndexes
-            false, // supportForSpatialIndexes
-            true,  // supportForKeyIndexes
-            true,  // supportForUniqueIndexes
-            true,  // supportForFulltextIndexes
-            true   // supportForTTLIndexes
-        );
+        $validator = new Index(
+            attributes: $collection->getAttribute('attributes'),
+            indexes: $collection->getAttribute('indexes', []),
+            maxLength: 768,
+            reservedKeys: [],
+            supportForArrayIndexes: false,
+            supportForSpatialIndexNull: false,
+            supportForSpatialIndexOrder: false,
+            supportForVectorIndexes: false,
+            supportForAttributes: true,
+            supportForMultipleFulltextIndexes: true,
+            supportForIdenticalIndexes: true,
+            supportForObjectIndexes: false,
+            supportForTrigramIndexes: false,
+            supportForSpatialIndexes: false,
+            supportForKeyIndexes: true,
+            supportForUniqueIndexes: true,
+            supportForFulltextIndexes: true,
+            supportForTTLIndexes: true,
+        );
tests/e2e/Adapter/Scopes/DocumentTests.php (2)

7431-7543: Make testTTLIndexes deterministic (auth state, datetime filter, and cleanup).

This test currently relies on ambient Authorization state and doesn’t guarantee cleanup if an assertion fails; also the expiresAt attribute is created without an explicit datetime filter (but later you do include it via the attribute Document).

Proposed hardening diff
 public function testTTLIndexes(): void
 {
     /** @var Database $database */
     $database = static::getDatabase();

     if (!$database->getAdapter()->getSupportTTLIndexes()) {
         $this->expectNotToPerformAssertions();
         return;
     }

-    $col = uniqid('sl_ttl');
-    $database->createCollection($col);
-
-    $database->createAttribute($col, 'expiresAt', Database::VAR_DATETIME, 0, false);
+    Authorization::cleanRoles();
+    Authorization::setRole(Role::any()->toString());
+
+    $col = 'sl_ttl_' . ID::unique();
+    $col2 = 'sl_ttl_collection_' . ID::unique();
+
+    try {
+        $database->createCollection($col);
+
+        $database->createAttribute(
+            $col,
+            'expiresAt',
+            Database::VAR_DATETIME,
+            0,
+            false,
+            filters: ['datetime']
+        );

     $permissions = [
         Permission::read(Role::any()),
         Permission::write(Role::any()),
         Permission::update(Role::any()),
         Permission::delete(Role::any())
     ];

     $this->assertTrue(
         $database->createIndex(
             $col,
             'idx_ttl_valid',
             Database::INDEX_TTL,
             ['expiresAt'],
             [],
             [Database::ORDER_ASC],
             3600 // 1 hour TTL
         )
     );

     $collection = $database->getCollection($col);
     $indexes = $collection->getAttribute('indexes');
     $this->assertCount(1, $indexes);
     $ttlIndex = $indexes[0];
     $this->assertEquals('idx_ttl_valid', $ttlIndex->getId());
     $this->assertEquals(Database::INDEX_TTL, $ttlIndex->getAttribute('type'));
     $this->assertEquals(3600, $ttlIndex->getAttribute('ttl'));

     $now = new \DateTime();
     $future1 = (clone $now)->modify('+2 hours');
     $future2 = (clone $now)->modify('+1 hour');
     $past = (clone $now)->modify('-1 hour');

     $database->createDocuments($col, [
         new Document([
             '$id' => 'doc1',
             '$permissions' => $permissions,
             'expiresAt' => $future1->format(\DateTime::ATOM),
         ]),
         new Document([
             '$id' => 'doc2',
             '$permissions' => $permissions,
             'expiresAt' => $future2->format(\DateTime::ATOM),
         ]),
         new Document([
             '$id' => 'doc3',
             '$permissions' => $permissions,
             'expiresAt' => $past->format(\DateTime::ATOM),
         ])
     ]);

     $this->assertTrue($database->deleteIndex($col, 'idx_ttl_valid'));

     $this->assertTrue(
         $database->createIndex(
             $col,
             'idx_ttl_min',
             Database::INDEX_TTL,
             ['expiresAt'],
             [],
             [Database::ORDER_ASC],
             1 // Minimum TTL
         )
     );

-    $col2 = uniqid('sl_ttl_collection');
-
     $expiresAtAttr = new Document([
         '$id' => ID::custom('expiresAt'),
         'type' => Database::VAR_DATETIME,
         'size' => 0,
         'signed' => false,
         'required' => false,
         'default' => null,
         'array' => false,
         'filters' => ['datetime'],
     ]);

     $ttlIndexDoc = new Document([
         '$id' => ID::custom('idx_ttl_collection'),
         'type' => Database::INDEX_TTL,
         'attributes' => ['expiresAt'],
         'lengths' => [],
         'orders' => [Database::ORDER_ASC],
         'ttl' => 7200 // 2 hours
     ]);

     $database->createCollection($col2, [$expiresAtAttr], [$ttlIndexDoc]);

     $collection2 = $database->getCollection($col2);
     $indexes2 = $collection2->getAttribute('indexes');
     $this->assertCount(1, $indexes2);
     $ttlIndex2 = $indexes2[0];
     $this->assertEquals('idx_ttl_collection', $ttlIndex2->getId());
     $this->assertEquals(7200, $ttlIndex2->getAttribute('ttl'));

-    $database->deleteCollection($col);
-    $database->deleteCollection($col2);
+    } finally {
+        // Best-effort cleanup (avoid leaking collections on assertion failures)
+        try { $database->deleteCollection($col); } catch (\Throwable) {}
+        try { $database->deleteCollection($col2); } catch (\Throwable) {}
+        Authorization::cleanRoles();
+    }
 }

7545-7690: Reduce brittleness in duplicate-TTL assertions + ensure $col3 can’t leak on unexpected success.

The createCollection duplicate test hard-codes a very specific error message (“Index already exists”) and doesn’t clean up $col3 if the call unexpectedly succeeds (it would fail() before cleanup).

Suggested diff
-        try {
-            $database->createCollection($col3, [$expiresAtAttr], [$ttlIndex1, $ttlIndex2]);
-            $this->fail('Expected exception for duplicate TTL indexes in createCollection');
-        } catch (Exception $e) {
-            $this->assertInstanceOf(DatabaseException::class, $e);
-            // raised in the mongo level
-            $this->assertStringContainsString('Index already exists', $e->getMessage());
-        }
+        try {
+            $database->createCollection($col3, [$expiresAtAttr], [$ttlIndex1, $ttlIndex2]);
+            // If we got here, make sure we don't leak the collection before failing the test.
+            $database->deleteCollection($col3);
+            $this->fail('Expected exception for duplicate TTL indexes in createCollection');
+        } catch (DatabaseException $e) {
+            // Raised in adapter/validator layer; message may vary.
+            $this->assertStringContainsString('already', strtolower($e->getMessage()));
+        }
src/Database/Database.php (1)

1633-1658: Risky IndexValidator ctor wiring: please verify all call sites + consider named args.
Adding one more boolean at the tail is easy to mis-order across the repo; a single missed instantiation will be a runtime fatal. Also, this constructor is a prime candidate for named arguments or a small config DTO to prevent future footguns.
Based on learnings, Mongo’s getMaxIndexLength() should be non-zero (1,048,576 bytes) so the length normalization logic/validation behaves as intended.

#!/bin/bash
set -euo pipefail

echo "== IndexValidator constructor signature =="
rg -n "class\s+Index\b" src/Database/Validator/Index.php || true
rg -n "function __construct\s*\(" src/Database/Validator/Index.php || true

echo
echo "== All IndexValidator instantiations (ensure extra arg was added everywhere) =="
rg -n "new\s+IndexValidator\s*\(" src -S

echo
echo "== Adapter capability method existence =="
rg -n "function\s+getSupportTTLIndexes\s*\(" src -S
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 43505a1 and 151703f.

📒 Files selected for processing (13)
  • src/Database/Adapter.php
  • src/Database/Adapter/MariaDB.php
  • src/Database/Adapter/Mongo.php
  • src/Database/Adapter/MySQL.php
  • src/Database/Adapter/Pool.php
  • src/Database/Adapter/Postgres.php
  • src/Database/Adapter/SQLite.php
  • src/Database/Database.php
  • src/Database/Mirror.php
  • src/Database/Validator/Index.php
  • tests/e2e/Adapter/Scopes/DocumentTests.php
  • tests/e2e/Adapter/Scopes/SchemalessTests.php
  • tests/unit/Validator/IndexTest.php
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: abnegate
Repo: utopia-php/database PR: 721
File: tests/e2e/Adapter/Scopes/AttributeTests.php:1329-1334
Timestamp: 2025-10-03T01:50:11.943Z
Learning: MongoDB has a 1024kb (1,048,576 bytes) limit for index entries. The MongoDB adapter's getMaxIndexLength() method should return this limit rather than 0.
📚 Learning: 2025-10-03T02:04:17.803Z
Learnt from: abnegate
Repo: utopia-php/database PR: 721
File: tests/e2e/Adapter/Scopes/DocumentTests.php:6418-6439
Timestamp: 2025-10-03T02:04:17.803Z
Learning: In tests/e2e/Adapter/Scopes/DocumentTests::testSchemalessDocumentInvalidInteralAttributeValidation (PHP), when the adapter reports getSupportForAttributes() === false (schemaless), the test should not expect exceptions from createDocuments for “invalid” internal attributes; remove try/catch and ensure the test passes without exceptions, keeping at least one assertion.

Applied to files:

  • tests/e2e/Adapter/Scopes/SchemalessTests.php
  • tests/unit/Validator/IndexTest.php
  • tests/e2e/Adapter/Scopes/DocumentTests.php
📚 Learning: 2025-10-16T09:37:33.531Z
Learnt from: fogelito
Repo: utopia-php/database PR: 733
File: src/Database/Adapter/MariaDB.php:1801-1806
Timestamp: 2025-10-16T09:37:33.531Z
Learning: In the MariaDB adapter (src/Database/Adapter/MariaDB.php), only duplicate `_uid` violations should throw `DuplicateException`. All other unique constraint violations, including `PRIMARY` key collisions on the internal `_id` field, should throw `UniqueException`. This is the intended design to distinguish between user-facing document duplicates and internal/user-defined unique constraint violations.

Applied to files:

  • tests/e2e/Adapter/Scopes/SchemalessTests.php
📚 Learning: 2025-10-29T12:27:57.071Z
Learnt from: ArnabChatterjee20k
Repo: utopia-php/database PR: 747
File: src/Database/Adapter/Mongo.php:1449-1453
Timestamp: 2025-10-29T12:27:57.071Z
Learning: In src/Database/Adapter/Mongo.php, when getSupportForAttributes() returns false (schemaless mode), the updateDocument method intentionally uses a raw document without $set operator for replacement-style updates, as confirmed by the repository maintainer ArnabChatterjee20k.

Applied to files:

  • src/Database/Adapter/Mongo.php
📚 Learning: 2025-10-16T08:48:36.715Z
Learnt from: fogelito
Repo: utopia-php/database PR: 739
File: src/Database/Adapter/Postgres.php:154-158
Timestamp: 2025-10-16T08:48:36.715Z
Learning: For the utopia-php/database repository, no migration scripts are needed for the collation change from utf8_ci to utf8_ci_ai in the Postgres adapter because there is no existing production data.

Applied to files:

  • src/Database/Adapter/Postgres.php
🧬 Code graph analysis (9)
tests/e2e/Adapter/Scopes/SchemalessTests.php (4)
src/Database/Database.php (2)
  • Database (37-8799)
  • createIndex (3620-3730)
src/Database/Adapter.php (2)
  • getSupportForAttributes (925-925)
  • createIndex (674-674)
src/Database/Adapter/SQL.php (1)
  • getSupportForAttributes (949-952)
src/Database/Adapter/SQLite.php (1)
  • createIndex (459-484)
src/Database/Mirror.php (3)
src/Database/Adapter/Pool.php (1)
  • createIndex (213-216)
src/Database/Adapter.php (1)
  • createIndex (674-674)
src/Database/Database.php (1)
  • createIndex (3620-3730)
src/Database/Adapter/MySQL.php (6)
src/Database/Adapter/MariaDB.php (1)
  • getSupportTTLIndexes (2259-2262)
src/Database/Adapter/Pool.php (1)
  • getSupportTTLIndexes (653-656)
src/Database/Adapter/Postgres.php (1)
  • getSupportTTLIndexes (2836-2839)
src/Database/Adapter/SQLite.php (1)
  • getSupportTTLIndexes (1913-1916)
src/Database/Adapter.php (1)
  • getSupportTTLIndexes (1494-1497)
src/Database/Adapter/Mongo.php (1)
  • getSupportTTLIndexes (3418-3421)
src/Database/Adapter/Pool.php (6)
src/Database/Adapter/MariaDB.php (2)
  • createIndex (718-781)
  • getSupportTTLIndexes (2259-2262)
src/Database/Adapter/Postgres.php (2)
  • createIndex (882-945)
  • getSupportTTLIndexes (2836-2839)
src/Database/Adapter/SQLite.php (2)
  • createIndex (459-484)
  • getSupportTTLIndexes (1913-1916)
src/Database/Adapter.php (2)
  • createIndex (674-674)
  • getSupportTTLIndexes (1494-1497)
src/Database/Adapter/Mongo.php (2)
  • createIndex (919-1047)
  • getSupportTTLIndexes (3418-3421)
src/Database/Adapter/MySQL.php (1)
  • getSupportTTLIndexes (321-324)
src/Database/Adapter/Mongo.php (5)
src/Database/Adapter.php (3)
  • filter (1257-1266)
  • createIndex (674-674)
  • getSupportTTLIndexes (1494-1497)
src/Database/Adapter/MariaDB.php (2)
  • createIndex (718-781)
  • getSupportTTLIndexes (2259-2262)
src/Database/Adapter/Pool.php (2)
  • createIndex (213-216)
  • getSupportTTLIndexes (653-656)
src/Database/Mirror.php (1)
  • createIndex (472-513)
src/Database/Adapter/MySQL.php (1)
  • getSupportTTLIndexes (321-324)
tests/unit/Validator/IndexTest.php (2)
src/Database/Database.php (1)
  • Database (37-8799)
src/Database/Validator/Index.php (3)
  • Index (10-796)
  • isValid (110-164)
  • getDescription (85-88)
src/Database/Validator/Index.php (1)
src/Database/Database.php (1)
  • Database (37-8799)
tests/e2e/Adapter/Scopes/DocumentTests.php (6)
src/Database/Adapter/MariaDB.php (1)
  • getSupportTTLIndexes (2259-2262)
src/Database/Adapter/MySQL.php (1)
  • getSupportTTLIndexes (321-324)
src/Database/Adapter/Pool.php (1)
  • getSupportTTLIndexes (653-656)
src/Database/Adapter/Postgres.php (1)
  • getSupportTTLIndexes (2836-2839)
src/Database/Adapter/SQLite.php (1)
  • getSupportTTLIndexes (1913-1916)
src/Database/Adapter/Mongo.php (1)
  • getSupportTTLIndexes (3418-3421)
src/Database/Adapter/Postgres.php (2)
src/Database/Adapter.php (2)
  • createIndex (674-674)
  • getSupportTTLIndexes (1494-1497)
src/Database/Database.php (1)
  • createIndex (3620-3730)
🪛 PHPMD (2.15.0)
src/Database/Adapter/MariaDB.php

718-718: Avoid unused parameters such as '$indexAttributeTypes'. (undefined)

(UnusedFormalParameter)


718-718: Avoid unused parameters such as '$collation'. (undefined)

(UnusedFormalParameter)


718-718: Avoid unused parameters such as '$ttl'. (undefined)

(UnusedFormalParameter)

src/Database/Adapter/Mongo.php

919-919: Avoid unused parameters such as '$lengths'. (undefined)

(UnusedFormalParameter)

src/Database/Adapter/SQLite.php

459-459: Avoid unused parameters such as '$lengths'. (undefined)

(UnusedFormalParameter)


459-459: Avoid unused parameters such as '$orders'. (undefined)

(UnusedFormalParameter)


459-459: Avoid unused parameters such as '$indexAttributeTypes'. (undefined)

(UnusedFormalParameter)


459-459: Avoid unused parameters such as '$collation'. (undefined)

(UnusedFormalParameter)


459-459: Avoid unused parameters such as '$ttl'. (undefined)

(UnusedFormalParameter)

src/Database/Adapter/Postgres.php

882-882: Avoid unused parameters such as '$lengths'. (undefined)

(UnusedFormalParameter)


882-882: Avoid unused parameters such as '$indexAttributeTypes'. (undefined)

(UnusedFormalParameter)


882-882: Avoid unused parameters such as '$collation'. (undefined)

(UnusedFormalParameter)


882-882: Avoid unused parameters such as '$ttl'. (undefined)

(UnusedFormalParameter)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Adapter Tests (Mirror)
  • GitHub Check: Adapter Tests (Postgres)
  • GitHub Check: Adapter Tests (MariaDB)
  • GitHub Check: Adapter Tests (Pool)
  • GitHub Check: Adapter Tests (SharedTables/MySQL)
  • GitHub Check: Adapter Tests (MySQL)
  • GitHub Check: Adapter Tests (SharedTables/Postgres)
  • GitHub Check: Adapter Tests (SharedTables/MariaDB)
🔇 Additional comments (21)
src/Database/Adapter/MySQL.php (1)

321-324: TTL capability flag is correctly exposed for MySQL (false).
This aligns with the adapter-wide API surface (Mongo true; others false).

src/Database/Adapter/Pool.php (2)

213-216: Pool::createIndex correctly forwards the expanded signature.
Minor note: callers must pass TTL as the last argument (or use named args) to avoid shifting into $indexAttributeTypes/$collation.


653-656: Pool::getSupportTTLIndexes delegation looks correct.

src/Database/Adapter/MariaDB.php (1)

2259-2262: TTL capability flag is correctly exposed for MariaDB (false).

tests/e2e/Adapter/Scopes/SchemalessTests.php (2)

9-10: New DatabaseException import alias is fine.


1870-2130: No changes needed. The createIndex calls are correct.

The Database::createIndex signature confirms ttl is properly exposed as the 7th positional parameter (int $ttl = 0), and all calls in the test pass the TTL value (3600, 7200, etc.) in the correct position: createIndex($col, $id, Database::INDEX_TTL, $attributes, [], $orders, $ttl).

tests/unit/Validator/IndexTest.php (1)

773-776: The TTL support flag is correctly targeted.

The instantiation passes 13 positional arguments after maxLength, which maps up to supportForTrigramIndexes. The remaining parameters, including supportForTTLIndexes, use their default value of false, which is the correct setting for a "no support" validator. The test assertion 'TTL indexes are not supported' matches the expected error message at line 231 in the validator.

However, note that the code comment on line 773 says "Validator with supportForTrigramIndexes disabled" when it should say "supportForTTLIndexes disabled"—the comment is misleading, though the test logic itself is correct.

src/Database/Adapter.php (2)

669-674: LGTM!

The abstract method signature is properly extended with optional parameters at the end, maintaining backward compatibility. The PHPDoc types are correctly specified.


1488-1497: LGTM!

The default implementation returning false is appropriate for a capability flag. Adapters that support TTL indexes (like MongoDB) can override this method to return true.

src/Database/Adapter/Postgres.php (3)

322-336: LGTM!

The TTL attribute extraction and propagation to createIndex is correctly implemented. This ensures consistent plumbing across all adapters, even though PostgreSQL doesn't natively support TTL indexes.


882-882: Unused parameters are expected for interface compliance.

The $collation and $ttl parameters (along with pre-existing $lengths and $indexAttributeTypes) are unused because PostgreSQL doesn't support TTL indexes natively. This is intentional - the signature must match the abstract method in Adapter.php. The static analysis warnings can be safely ignored in this context.


2835-2839: LGTM!

Correctly returns false as PostgreSQL doesn't support TTL indexes natively.

src/Database/Adapter/SQLite.php (3)

219-221: LGTM!

The TTL attribute extraction and propagation follows the same pattern as other adapters, maintaining consistency across the codebase.


459-459: Unused parameters are expected for interface compliance.

The $collation and $ttl parameters (along with pre-existing $lengths, $orders, and $indexAttributeTypes) are unused because SQLite doesn't support TTL indexes. This matches the expected pattern for adapters that don't implement this feature. The static analysis warnings can be safely ignored.


1912-1916: LGTM!

Correctly returns false as SQLite doesn't support TTL indexes.

src/Database/Validator/Index.php (1)

779-792: Verify duplicate TTL index detection logic.

The condition $this->supportForAttributes && $existingType !== Database::INDEX_TTL at line 783 means duplicate detection is skipped for non-TTL indexes only when supportForAttributes is true. This coupling seems unintentional — duplicate TTL index detection should likely occur regardless of the supportForAttributes flag.

Consider simplifying the condition to only skip non-TTL indexes:

-            if ($this->supportForAttributes && $existingType !== Database::INDEX_TTL) {
+            if ($existingType !== Database::INDEX_TTL) {
                 continue;
             }
src/Database/Adapter/Mongo.php (4)

511-538: LGTM! TTL index support correctly implemented for collection creation.

The TTL index handling follows MongoDB's expireAfterSeconds pattern correctly, and the case handling integrates well with existing index type processing.


915-981: TTL index creation correctly implemented.

The implementation properly sets expireAfterSeconds when $ttl > 0 for MongoDB TTL indexes.

Note: The $lengths parameter is intentionally unused as MongoDB doesn't support index prefix lengths like SQL databases. This is consistent with the adapter's design.


1093-1098: LGTM! TTL value preserved during index rename.

The renameIndex method correctly retrieves and passes the existing TTL value when recreating the index with the new name.


3417-3421: LGTM! TTL support capability correctly exposed.

This enables the validator to properly gate TTL index creation for MongoDB.

src/Database/Database.php (1)

79-90: INDEX_TTL constant addition looks good (no collision with cache TTL).

return false;
}

public function getSupportTTLIndexes(): bool
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make sure method names are always consistent: getSupportForTTLIndexes


// If sharedTables, always add _tenant as the first key
if ($this->sharedTables) {
if ($this->sharedTables && $index->getAttribute('type') !== Database::INDEX_TTL) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make this check an (adapter?) method, shouldAddTenantToIndex

Comment on lines 760 to 763
if (empty($orders)) {
$this->message = 'TTL index need explicit orders. Add the orders to create this index.';
return false;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's default to ASC

Comment on lines 774 to 777
if ($ttl <= 0) {
$this->message = 'TTL must be at least 1 second';
return false;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this disallow indexes without a TTL since 0 is the default?

Copy link
Contributor Author

@ArnabChatterjee20k ArnabChatterjee20k Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed the default to 1

);
$this->fail('Expected exception for duplicate TTL index on same attribute');
} catch (Exception $e) {
$this->assertInstanceOf(DatabaseException::class, $e);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be an IndexException

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In IndexTests everyplace we are using DatabaseException

}
}

public function testTTLIndexes(): void
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see where we test that the documents were removed after TTL expiry

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mongo doesn't directly delete after the ttl, it takes time to delete. Was getting tooo flaky

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated with retries due to flakiness

@ArnabChatterjee20k ArnabChatterjee20k marked this pull request as draft January 12, 2026 09:48
ArnabChatterjee20k and others added 8 commits January 13, 2026 20:53
- Updated assertions in existing tests to compare datetime values by parsing them into DateTime objects, ensuring accurate comparisons with MongoDB's format.
- Added new test `testSchemalessDatetimeCreationAndFetching` to verify creation, fetching, and updating of ISO 8601 datetime strings.
- Introduced `testSchemalessTTLExpiry` to validate TTL functionality with mixed document types, ensuring expired documents are correctly removed.
- Implemented `testStringAndDatetime` and `testStringAndDateWithTTL` to check behavior of string and datetime fields, including handling of TTL indexes.
Co-authored-by: Jake Barnby <jakeb994@gmail.com>
@ArnabChatterjee20k ArnabChatterjee20k marked this pull request as ready for review January 14, 2026 06:48
}

// Check if existing index is also a TTL index
if ($existingIndex->getAttribute('type') === Database::INDEX_TTL) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@abnegate restricting to a single ttl index per collection for deterministic expiration on the cache

@ArnabChatterjee20k ArnabChatterjee20k marked this pull request as draft January 14, 2026 08:37
@ArnabChatterjee20k ArnabChatterjee20k changed the base branch from 3.x to main January 22, 2026 13:52
@ArnabChatterjee20k ArnabChatterjee20k marked this pull request as ready for review January 22, 2026 13:53
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@composer.json`:
- Line 42: The composer.json dependency "utopia-php/pools": "1.*" is invalid;
update the constraint to a real published version (for example change
"utopia-php/pools": "0.8.*") if the code is compatible, or revert this change
and ensure the PR is held until utopia-php/pools v1.0 is officially released;
modify the dependency string in composer.json accordingly and run composer
validate/install to confirm the fix.

"utopia-php/framework": "0.33.*",
"utopia-php/cache": "0.13.*",
"utopia-php/pools": "0.8.*",
"utopia-php/pools": "1.*",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find Pool-related imports and usages in the codebase

echo "=== Pool imports ==="
rg -n --type php 'use Utopia\\Pools' --glob '!vendor/*'

echo ""
echo "=== Pool class instantiation ==="
rg -n --type php 'new Pool\|->pool\|Pool::' --glob '!vendor/*' -C2

echo ""
echo "=== Mirror adapter usage ==="
rg -n --type php 'Mirror' --glob '!vendor/*' -C2 | head -50

Repository: utopia-php/database

Length of output: 3223


🌐 Web query:

utopia-php/pools 1.0 release changelog breaking changes

💡 Result:

I couldn't find a utopia-php/pools v1.0 release or a 1.0 changelog. The latest released tag listed on Packagist is v0.8.2 (last updated Sep 26, 2025). [1]

If you want, I can check the GitHub releases/tags page or repository commit history for any unpublished v1.0 notes—shall I do that?

Sources:


🏁 Script executed:

#!/bin/bash
# Check Pool adapter implementation and composer.json context

echo "=== Pool adapter implementation ==="
head -50 src/Database/Adapter/Pool.php

echo ""
echo "=== Full composer.json for context ==="
cat composer.json | grep -A5 -B5 'utopia-php/pools'

echo ""
echo "=== Check for repositories/VCS config in composer.json ==="
rg -n '"repositories"|"require-dev"|"utopia-php/pools"' composer.json -C2

Repository: utopia-php/database

Length of output: 2404


🌐 Web query:

utopia-php/pools repository github tags releases dev-master 1.0

💡 Result:

The utopia-php/pools package's latest released version on Packagist is 0.8.2 (no 1.0 release listed). Packagist/Libraries.io show current published versions (no 1.0). [1][2]

If you need the repository's branch names (e.g., dev-master) or Git tags directly, check the GitHub repo tags/releases page (github.com/utopia-php/pools) — Packagist reflects published Composer versions. [1][2]

Sources:

  • Packagist package page for utopia-php/pools. [1]
  • Libraries.io / Packagist mirror for utopia-php/pools. [2]

🏁 Script executed:

#!/bin/bash
# Check complete composer.json structure for any special configurations

echo "=== Entire composer.json ==="
cat composer.json

echo ""
echo "=== Check git references to pools library ==="
rg 'pools' .gitmodules 2>/dev/null || echo "No git submodules found"

Repository: utopia-php/database

Length of output: 2369


🌐 Web query:

utopia-php/pools GitHub repository tags releases main dev-main

💡 Result:

Summary I found on the package registry:

  • Latest published tag/release: 0.8.2 (tagged 2025-04-17) for utopia-php/pools. [1]

The package source is on GitHub (github.com/utopia-php/pools). Would you like me to open the GitHub repo now and list its tags/releases and branches (main, dev-main) directly?


Fix invalid dependency constraint for utopia-php/pools.

The version 1.* does not exist on Packagist or in any public repository. The latest available release is 0.8.2. This constraint will cause composer install to fail.

Either correct the version to 0.8.* if the code is compatible with the current release, or verify this PR should only be merged after utopia-php/pools v1.0 is officially released.

🤖 Prompt for AI Agents
In `@composer.json` at line 42, The composer.json dependency "utopia-php/pools":
"1.*" is invalid; update the constraint to a real published version (for example
change "utopia-php/pools": "0.8.*") if the code is compatible, or revert this
change and ensure the PR is held until utopia-php/pools v1.0 is officially
released; modify the dependency string in composer.json accordingly and run
composer validate/install to confirm the fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants